package com.zmn.oms.zmn.business.impl.chan;

import com.alibaba.fastjson.JSON;
import com.zmn.common.constant.StatusConsts;
import com.zmn.manager.redis.RedisManager;
import com.zmn.oms.common.constant.OrderClueConsts;
import com.zmn.oms.common.constant.RedisKeyConsts;
import com.zmn.oms.common.enums.ChannelFacade;
import com.zmn.oms.common.exception.chan.ChannelFacadeException;
import com.zmn.oms.common.exception.chan.DuplicateOrderClueException;
import com.zmn.oms.model.entity.chan.FacadeMessage;
import com.zmn.oms.model.entity.chan.FacadeMessageEvent;
import com.zmn.oms.model.entity.chan.OrderClue;
import com.zmn.oms.model.vo.work.ZsOrderWorkVO;
import com.zmn.oms.services.interfaces.chan.OrderClueService;
import com.zmn.oms.zmn.business.interfaces.chan.FacadeMessageBService;
import com.zmn.oms.zmn.business.interfaces.chan.FacadeOrderClueBService;
import com.zmn.oms.zmn.business.interfaces.work.ZsOrderWorkQueryBService;
import io.shardingsphere.core.keygen.DefaultKeyGenerator;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Objects;
import java.util.Optional;

/**
 * 类描述：
 *
 * @author lhl
 * @date 2020/12/29 16:28
 */
@Slf4j
@Service("facadeOrderClueBService")
public class FacadeOrderClueBServiceImpl implements FacadeOrderClueBService {

    @Autowired
    private OrderClueService orderClueService;
    @Autowired
    private FacadeMessageBService facadeMessageBServices;
    @Autowired
    private ZsOrderWorkQueryBService zsOrderWorkQueryBService;

    @Autowired
    private RedisManager redisManager;
    @Autowired
    private DefaultKeyGenerator defaultKeyGenerator;


    /**
     *  保存渠道订单线索索引
     * @param orderClue 渠道订单线索索引对象，该对象的 facadeId 和 outerId 字段不能为null
     * @param facadeMessage 渠道消息元数据，该对象的 facadeId、eventTag、content 字段不能为null
     * @param generateFacadeOrderId 是否生成需要返回给渠道方的啄木鸟订单ID，如果为 true，生成的ID将
     *                              在方法调用结束后保存在 orderClue 参数对象的 facadeOrderId 字段中
     * @throws DuplicateOrderClueException 当判定该线索为重复收到时被抛出，
     *                              判定条件为：orderClue.facadeId 和 orderClue.outerId 组合值是否唯一
     * @throws ChannelFacadeException 当参数不全或保存失败时被抛出
     */
    @Transactional
    @Override
    public void saveOrderClue(OrderClue orderClue, FacadeMessage facadeMessage, boolean generateFacadeOrderId)
            throws DuplicateOrderClueException, ChannelFacadeException {
        // 校验参数完整性
        if (orderClue == null || orderClue.getFacadeId() == null || StringUtils.isBlank(orderClue.getOuterId())) {
            throw new ChannelFacadeException("参数不全");
        }
        if (facadeMessage == null || facadeMessage.getFacadeId() == null || facadeMessage.getEventTag() == null
                || StringUtils.isBlank(facadeMessage.getContent())) {
            throw new ChannelFacadeException("订单消息信息不全");
        }
        if (orderClue.getFacadeId().intValue() != facadeMessage.getFacadeId().intValue()) {
            throw new ChannelFacadeException("渠道门户ID不一致");
        }

        // 提取多次使用的变量：渠道门户ID，渠道单号，渠道ID
        Integer facadeId = orderClue.getFacadeId();
        String outerId = orderClue.getOuterId();
        Integer channelId = Optional.ofNullable(orderClue.getChannelId())
                .filter(val -> val.intValue() != 0)
                .orElse(null);

        // 某些渠道门户接口（比如OpenAPI）进来的订单线索，必须指定具体的渠道ID
        if (channelId == null) {
            ChannelFacade channelFacade = ChannelFacade.getByFacadeId(facadeId);
            if (channelFacade.isRequireChannelId()) {
                throw new ChannelFacadeException(String.format("渠道门户[%s]必须指定具体的渠道ID", channelFacade.getName()));
            }
        }

        DuplicateClueValidater validater = this.checkDuplicate(facadeId, outerId, channelId);
        if (validater.isDupicate()) {
            OrderClue dbOrderClue = orderClueService.findByFacadeIdAndOuterId(facadeId, outerId, channelId);
            if (dbOrderClue != null) {
                // 重复的订单消息，已生成新单线索的（通过渠道门户接入的订单），记录这条重复消息
                try {
                    int messageNo = orderClueService.updateMessageNoByKey(dbOrderClue.getId());
                    facadeMessage.setClueId(dbOrderClue.getId());
                    facadeMessage.setMessageNo(messageNo);
                    facadeMessageBServices.save(facadeMessage);
                } catch (ChannelFacadeException e) {
                }
            }
            throw new DuplicateOrderClueException("重复的线索数据");
        }

        DuplicateOrderClueException docException = null;
        try {
            // 渠道订单消息的编号默认从1开始
            orderClue.setMessageNo(1);
            if (generateFacadeOrderId) {
                orderClue.setFacadeOrderId(defaultKeyGenerator.generateKey().longValue());
            }

            try {
                orderClueService.save(orderClue);
            } catch (DuplicateKeyException e) {
                docException = new DuplicateOrderClueException("重复的线索数据");
            }

            // 重复的订单消息，不生成新线索，但保存该消息
            if (docException != null) {
                OrderClue dbOrderClue = orderClueService.findByFacadeIdAndOuterId(facadeId, outerId, channelId);
                int messageNo = orderClueService.updateMessageNoByKey(dbOrderClue.getId());
                facadeMessage.setClueId(dbOrderClue.getId());
                facadeMessage.setMessageNo(messageNo);
            }
            else {
                facadeMessage.setClueId(orderClue.getId());
                facadeMessage.setMessageNo(orderClue.getMessageNo());
            }
            facadeMessageBServices.save(facadeMessage);

            if (docException != null) {
                throw docException;
            }
        } catch (Exception e) {
            log.error(String.format("保存线索失败: %s，OrderClue=[%s], FacadeMessage=[%s], generateFacadeOrderId=[%s]",
                    e.getMessage(), JSON.toJSONString(orderClue), JSON.toJSONString(facadeMessage),
                    String.valueOf(generateFacadeOrderId)), e);

            if (e == docException) {// 线索重复
                throw e;
            }

            validater.invalid();
            if (e instanceof ChannelFacadeException) {
                throw e;
            }
            throw new ChannelFacadeException(e.getMessage(), e);
        }
    }

    /**
     * 保存订单的业务消息，并返回对应的订单线索；当未找到对应的订单线索时，返回null，并且消息不会被保存
     * @param facadeMessage 渠道消息元数据，该对象的 facadeId、eventTag、content 字段不能为null
     * @param outerId 渠道订单号
     * @param channelId 渠道ID
     * @return 对应的订单线索，当订单线索不存在时，返回null
     * @throws ChannelFacadeException 当参数不全或保存消息失败时被抛出
     */
    @Transactional
    @Override
    public OrderClue saveOrderBizMessage(FacadeMessage facadeMessage, String outerId, Integer channelId)
            throws ChannelFacadeException {
        // 校验参数完整性
        if (StringUtils.isBlank(outerId)) {
            throw new ChannelFacadeException("渠道订单号不能为空");
        }
        if (facadeMessage == null || facadeMessage.getFacadeId() == null || facadeMessage.getEventTag() == null
                || StringUtils.isBlank(facadeMessage.getContent())) {
            throw new ChannelFacadeException("订单消息信息不全");
        }

        int facadeId = facadeMessage.getFacadeId();
        Integer chanId = Optional.ofNullable(channelId)
                .filter(val -> val.intValue() != 0)
                .orElse(null);

        // 某些渠道门户接口（比如OpenAPI）进来的订单线索，必须指定具体的渠道ID
        if (chanId == null) {
            ChannelFacade channelFacade = ChannelFacade.getByFacadeId(facadeId);
            if (channelFacade.isRequireChannelId()) {
                throw new ChannelFacadeException(String.format("渠道门户[%s]必须指定具体的渠道ID", channelFacade.getName()));
            }
        }

        OrderClue dbOrderClue = orderClueService.findByFacadeIdAndOuterId(facadeId, outerId, chanId);
        if (dbOrderClue == null) {
            log.warn("未找到对应的订单线索: FacadeMessage=[{}], outerId=[{}], channelId=[{}]",
                    JSON.toJSONString(facadeMessage), outerId, channelId);
            return null;    // 兼容未使用线索池接入的订单，暂不报错
        }

        try {
            int messageNo = orderClueService.updateMessageNoByKey(dbOrderClue.getId());
            dbOrderClue.setMessageNo(messageNo);

            facadeMessage.setClueId(dbOrderClue.getId());
            facadeMessage.setMessageNo(messageNo);
            facadeMessageBServices.save(facadeMessage);
            return dbOrderClue;
        } catch (Exception e) {
            log.error(String.format("保存订单业务消息失败: %s，FacadeMessage=[%s], outerId=[%s], channelId=[%s]",
                    e.getMessage(), JSON.toJSONString(facadeMessage), outerId, String.valueOf(channelId)), e);
            throw new ChannelFacadeException(e.getMessage(), e);
        }
    }

    /**
     * 收到订单的业务消息，保存消息并获取对应的订单线索
     * @param event
     * @param outerId 渠道订单号
     * @return 对应的订单线索
     * @throws ChannelFacadeException 当未找到对应的订单线索或保存消息失败时被抛出
     */
    @Transactional
    @Override
    public OrderClue receiveOrderBizMessage(FacadeMessageEvent event,
                                            String outerId) throws ChannelFacadeException {
        Optional.ofNullable(event)
                .filter(e -> e.getTag() != null && StringUtils.isNotBlank(e.getContent()))
                .orElseThrow(() -> new ChannelFacadeException("订单消息信息不全"));

        int facadeId = event.getFacadeId();
        Integer channelId = Optional.ofNullable(event.getChannelId())
                .filter(val -> val.intValue() != 0)
                .orElse(null);
        OrderClue orderClue = orderClueService.findByFacadeIdAndOuterId(facadeId, outerId, channelId);
        if (orderClue == null) {
            log.warn("未找到对应的订单线索: facadeId=[{}], outerId=[{}], channelId=[{}]", facadeId, outerId, channelId);

            return null;    // 兼容未使用线索池接入的订单，暂不报错
            //throw new ChannelFacadeException("未找到对应的订单线索");
        }

        try {
            int messageNo = orderClueService.updateMessageNoByKey(orderClue.getId());
            orderClue.setMessageNo(messageNo);

            FacadeMessage facadeMessage = new FacadeMessage();
            facadeMessage.setEventTag(event.getTag());
            facadeMessage.setEventFun(event.getFun());
            facadeMessage.setClueId(orderClue.getId());
            facadeMessage.setFacadeId(orderClue.getFacadeId());
            facadeMessage.setContent(event.getContent());
            facadeMessage.setMessageNo(messageNo);
            facadeMessageBServices.save(facadeMessage);
            return orderClue;
        } catch (Exception e) {
            log.error(String.format("保存订单业务消息失败: %s，facadeId=[%s], outerId=[%s], content=[%s], channelId=[%s]",
                    e.getMessage(), String.valueOf(facadeId), outerId, event.getContent(), String.valueOf(facadeId)), e);
            throw new ChannelFacadeException(e.getMessage(), e);
        }
    }

    @Override
    public OrderClue findOrderClueByKey(Long key) {
        return orderClueService.findByKey(key);
    }

    /**
     * 保存啄木鸟系统单号
     * @param key
     * @param zmnOrderId
     * @param state
     * @return
     */
    @Override
    public int saveZmnOrderIdByKey(Long key, Long zmnOrderId, Integer state) {
        return orderClueService.updateZmnOrderIdByKey(key, zmnOrderId, state);
    }

    /**
     * 通过主键修改状态
     * @param key
     * @param state
     * @return
     */
    @Override
    public int updateStateByKey(Long key, Integer state) {
        return orderClueService.updateStateByKey(key, state);
    }

    @Override
    public OrderClue findOrderClueByFacadeIdAndOuterId(Integer facadeId, String outerId, Integer channelId) {
        return orderClueService.findByFacadeIdAndOuterId(facadeId, outerId, channelId);
    }

    @Override
    public OrderClue findByFacadeOrderId(Long facadeOrderId) {
        return orderClueService.findByFacadeOrderId(facadeOrderId);
    }

    @Override
    public OrderClue findByZmnOrderId(Long zmnOrderId) {
        return orderClueService.findByZmnOrderId(zmnOrderId);
    }

    /**
     * 判断是否是重复的新单消息, 如果是重复消息，返回true
     * @param facadeId
     * @param outerId
     * @param channelId 可以为null
     * @return
     */
    private DuplicateClueValidater checkDuplicate(Integer facadeId, String outerId, Integer channelId) {
        /*
         * 新单消息去重需要做两方面处理：
         *  1. 采用redis计数器判断该订单在一天内是否重复收到过，得出是否为重复消息；
         *  2. 订单在录单超过一天后，还是有可能推该单的新单消息，针对这种情况，通过outerId查看数据库中是否有该订单，判断其是否为重复；
         */
        try {// 方案1去重
            String redisKey = (channelId == null)
                    ? String.format(RedisKeyConsts.CHAN_FACADE_CLUE_ID, String.valueOf(facadeId), outerId)
                    : String.format(RedisKeyConsts.CHAN_CHANNEL_CLUE_ID, String.valueOf(channelId), outerId);
            Long num = redisManager.incr(redisKey);
            if (num.intValue() > 1) {
                return new DuplicateClueValidater(redisKey, true);
            }
            // 设置自动过期，清除Redis占用
            redisManager.expire(redisKey, RedisKeyConsts.CHAN_CLUE_ID_EXPIRE_SECONDS);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }


        {// 方案2去重
            ZsOrderWorkVO orderWorkVO = null;
            if (channelId == null) {
                // TODO -lhl 通过facadeId查channelId
            }
            else {
                orderWorkVO = zsOrderWorkQueryBService.findOrderWorkByOuterId(outerId, channelId);
            }

            if (orderWorkVO != null) {
                return new DuplicateClueValidater(null, true);
            }
        }

        return new DuplicateClueValidater(null, false);
    }

    /**
     * 重复订单线索校验器
     */
    private class DuplicateClueValidater {

        private String redisKey;
        private boolean dupicate = false;

        /**
         * 构造器
         * @param redisKey
         * @param isDupicate
         */
        public DuplicateClueValidater(String redisKey, boolean isDupicate) {
            this.redisKey = redisKey;
            this.dupicate = isDupicate;
        }

        /**
         * 是否是重复线索
         * @return
         */
        public boolean isDupicate() {
            return this.dupicate;
        }

        /**
         * 删除本次校验记录
         */
        public void invalid() {
            if (StringUtils.isNotBlank(this.redisKey)) {
                try {
                    redisManager.del(this.redisKey);
                } catch (Exception e) {
                    log.error(e.getMessage(), e);
                }
            }
        }
    }
}
